/*
 * Copyright 2002 Christopher L. Conway, Cheng-Hong Li and Megan Pengelly.
 *
 * This file is part of The Pencil Compiler.
 *
 * The Pencil Compiler is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Pencil Compiler is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with The Pencil Compiler; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* @author Megan Pengelly */
package math.petrinets.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.text.MaskFormatter;

import lpsolve.LpSolveException;
import math.petrinets.lpsolve.Simplex;

import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxStylesheet;

public class PetriNet implements Runnable {

    // Maths
    public int V[] = null;
    public int d[] = null;
    public int M0[] = null;
    public int Mf[] = null;
    public int C[][] = null;
    public int Pre[][] = null;
    public int Post[][] = null;

    private boolean running = true;
    private mxGraph graph;
    private JButton runButton;
    private JTextArea areaResult;
    private JTextArea textAreaMatrix;

    private ArrayList<Place> places;
    private ArrayList<Transition> transitions;
    private ArrayList<Transition> enableTransition;
    private ArrayList<Transition> transitionShooted;

    private Map<mxCell, Place> cellPlaces;
    private mxGraphComponent graphComponent;
    private Simplex simplex;

    private Thread petriThread;

    public PetriNet() {
	places = new ArrayList<Place>();
	transitions = new ArrayList<Transition>();
	cellPlaces = new HashMap<mxCell, Place>();
	enableTransition = new ArrayList<Transition>();
	transitionShooted = new ArrayList<Transition>();
	simplex = new Simplex();
	// init();
    }

    public static PetriNet createPetriNet(int[][] C, int[][] Pre, int[] M0, int[] Mf, int[] d) {
	PetriNet test = new PetriNet();
	if (C != null && M0 != null) {
	    Hashtable<String, Place> P = new Hashtable<String, Place>();
	    Hashtable<String, Transition> T = new Hashtable<String, Transition>();
	    for (int i = 0; i < C.length; i++) {
		Place p = new Place(M0[i]);
		p.setName("P" + i);
		p.setPosition(i);
		P.put(p.getName(), p);
	    }
	    for (int j = 0; j < C[0].length; j++) {
		Transition t = new SimpleTransition();
		t.setPosition(j);
		t.setName("t" + j);
		T.put(t.getName(), t);
	    }
	    test.Post = new int[C.length][C[0].length];
	    for (int i = 0; i < C.length; i++) {
		for (int j = 0; j < C[i].length; j++) {
		    test.Post[i][j] = C[i][j] + Pre[i][j];
		    Transition t = T.get("t" + j);
		    Place p = P.get("P" + i);
		    if (test.Post[i][j] > 0) {
			t.addOut(p, test.Post[i][j]);
		    }
		    if (Pre[i][j] > 0) {
			t.addIn(p, Pre[i][j]);
		    }
		}
	    }
	    // adding time to transition
	    for (int index = 0; index < d.length; index++) {
		Transition transition = T.get("t" + index);
		transition.setProcessTime(d[index]);
	    }
	    // // adding places to petri-net
	    for (int i = 0; i < C.length; i++) {
		Place p = P.get("P" + i);
		test.add(p);
	    }
	    // adding transition to petri-net
	    for (int j = 0; j < C[0].length; j++) {
		Transition t = T.get("t" + j);
		test.add(t);
	    }
	    test.C = C;
	    test.Pre = Pre;
	    test.M0 = M0;
	    test.Mf = Mf;
	    // test.V = V;
	    test.d = d;
	    return test;
	}
	return null;
    }

    public void init() {
	JFrame frame = new JFrame();
	frame.setLayout(new BorderLayout());
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

	graph = new mxGraph() {
	    public void drawState(mxICanvas canvas, mxCellState state, boolean drawLabel) {
		super.drawState(canvas, state, drawLabel);
		if (!(canvas instanceof mxGraphics2DCanvas)) {
		    // drag&drop ignored
		    return;
		}

		if (model.isVertex(state.getCell())) {
		    if (cellPlaces.containsKey(state.getCell())) {
			Graphics2D g = ((mxGraphics2DCanvas) canvas).getGraphics();
			// Object userValue = model.getValue(state.getCell());

			Place place = cellPlaces.get(state.getCell());

			// String textToDisplay = userValue.toString();
			String textToDisplay = place.getName();

			// do something with userValue
			if (textToDisplay != null) {
			    Font scaledFont = mxUtils.getFont(state.getStyle(), canvas.getScale());
			    g.setFont(scaledFont);
			    FontMetrics fm = g.getFontMetrics();
			    int w = SwingUtilities.computeStringWidth(fm, textToDisplay);
			    int h = fm.getAscent();
			    Color fontColor = mxUtils.getColor(state.getStyle(), mxConstants.STYLE_FONTCOLOR, Color.black);
			    g.setColor(fontColor);
			    g.drawString(textToDisplay, (int) state.getX() + (int) state.getWidth() + canvas.getTranslate().x - w, (int) state.getY() + (int) state.getHeight()
				    + canvas.getTranslate().y + h);
			}
		    }
		}
	    }
	};

	graph.setCellsDisconnectable(false);
	graph.setConnectableEdges(false);

	graphComponent = new mxGraphComponent(graph);

	frame.getContentPane().add(graphComponent);

	JScrollPane scrollPane = new JScrollPane();
	scrollPane.getViewport().add(graphComponent);

	JToolBar toolBar = new JToolBar();

	runButton = new JButton("Run");
	runButton.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		runButtonActionPerformed(e);
	    }
	});
	toolBar.add(runButton);

	areaResult = new JTextArea();
	areaResult.setEditable(false);
	
	JSplitPane verticalSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, areaResult);
	verticalSplit.setOneTouchExpandable(true);
	verticalSplit.setDividerLocation(400);

	textAreaMatrix = new JTextArea();
	textAreaMatrix.setEditable(false);
	
	JScrollPane splitScroll = new JScrollPane();
	splitScroll.getViewport().add(textAreaMatrix);
	
	JSplitPane horizontalSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, verticalSplit, splitScroll);
	horizontalSplit.setOneTouchExpandable(true);
	horizontalSplit.setDividerSize(5);
	horizontalSplit.setDividerLocation(550);
	
	frame.add(toolBar, BorderLayout.NORTH);
	frame.add(horizontalSplit, BorderLayout.CENTER);
	frame.pack(); // set window to appropriate size (for its elements)
	frame.setBounds(10, 10, 700, 600); // this time use a predefined frame
					   // size/position
	frame.setVisible(true); // usual step to make frame visible
    }

    private void runButtonActionPerformed(ActionEvent e) {
	petriThread = new Thread(this);
	petriThread.start();
    }

    public void destroy() {
	running = false;
	petriThread = null;
    }

    protected void add(Transition t) {
	synchronized (transitions) {
	    transitions.add(t);
	}
    }

    public Transition getTransition(int position) {
	for (Transition t : transitions) {
	    if (t.getPosition() == position) {
		return t;
	    }
	}
	return null;
    }

    protected void add(Place p) {
	synchronized (places) {
	    places.add(p);
	}
    }

    public ArrayList<Place> getPlaces() {
	return places;
    }

    protected void fire(Transition t) {
	synchronized (places) {
	    while (!isEnabled(t))
		try {
		    places.wait();
		} catch (InterruptedException e) {
		    System.out.println(e.getStackTrace());
		}
	    fireMark(t, places, false);
	}
    }

    private boolean isEnabled(Transition transition) {
	boolean enabled = true;
	synchronized (places) {
	    Iterator arcs = transition.getIn().iterator();
	    while (arcs.hasNext()) {
		Arc arc = (Arc) arcs.next();
		enabled &= (arc.getWeight() <= arc.getPlace().getTokens());
	    }
	    arcs = transition.getOut().iterator();
	    while (arcs.hasNext()) {
		Arc arc = (Arc) arcs.next();
		int bound = arc.getBound();
		enabled &= (bound < 0 || arc.getWeight() + arc.getPlace().getTokens() <= bound);
	    }
	}
	return enabled;
    }

    /**
     * Set values for graphics
     * 
     * @param place
     * @param token
     */
    private void drawTokens(Place place, int token) {
	mxCell cell = place.getCell();
	cell.setValue(token);
    }

    public void fireMark(Transition t, ArrayList<Place> places, boolean isCaliz) {
	synchronized (places) {
	    Iterator arcs = t.getIn().iterator();
	    while (arcs.hasNext()) {
		Arc arc = (Arc) arcs.next();
		Place place = arc.getPlace();
		int token = place.getTokens() - arc.getWeight();

		// modificar tokens en place del ArrayList
		Place tempPlace = places.get(place.getPosition());
		tempPlace.setTokens(token);

		if (!isCaliz) {
		    drawTokens(place, token);
		}

	    }
	    arcs = t.getOut().iterator();
	    while (arcs.hasNext()) {
		Arc arc = (Arc) arcs.next();
		Place place = arc.getPlace();
		int token = place.getTokens() + arc.getWeight();

		Place tempPlace = places.get(place.getPosition());
		tempPlace.setTokens(token);

		if (!isCaliz) {
		    drawTokens(place, token);
		}
	    }
	    t.onFire();
	    places.notifyAll();

	    if (!isCaliz) {
		graph.refresh();
		graphComponent.refresh();
	    }
	}
    }

    private void insertPlace(Place place, int x, int y) {
	graph.getModel().beginUpdate();
	try {
	    setStyleSheet(mxConstants.SHAPE_ELLIPSE);
	    Object cell = graph.insertVertex(graph.getDefaultParent(), null, place.getTokens(), x, y, 30, 30, mxConstants.SHAPE_ELLIPSE);
	    ((mxCell) cell).setConnectable(false);
	    place.setCell(cell);

	    cellPlaces.put((mxCell) cell, place);
	} finally {
	    graph.getModel().endUpdate();
	}
    }

    private void insertTransition(Transition transition, int x, int y) {
	graph.getModel().beginUpdate();
	try {
	    setStyleSheet(mxConstants.SHAPE_RECTANGLE);
	    Object cell = graph.insertVertex(graph.getDefaultParent(), null, transition.getName(), x, y, 10, 30, mxConstants.SHAPE_RECTANGLE);
	    ((mxCell) cell).setConnectable(false);
	    transition.setCell(cell);
	} finally {
	    graph.getModel().endUpdate();
	}
    }

    private void insertEdge(Arc arc, Object obj1, Object obj2) {
	graph.getModel().beginUpdate();
	try {
	    Object edge = graph.insertEdge(graph.getDefaultParent(), null, arc.getWeight(), obj1, obj2);
	    arc.setEdge(edge);
	} finally {
	    graph.getModel().endUpdate();
	}
    }

    private void setStyleSheet(String shape) {
	mxStylesheet stylesheet = graph.getStylesheet();
	Hashtable<String, Object> style = new Hashtable<String, Object>();
	style.put(mxConstants.STYLE_SHAPE, shape);
	style.put(mxConstants.STYLE_OPACITY, 50);
	style.put(mxConstants.STYLE_FONTCOLOR, "#774400");
	style.put(mxConstants.STYLE_EDITABLE, 0);
	style.put(mxConstants.STYLE_RESIZABLE, 0);
	style.put(mxConstants.STYLE_DELETABLE, 0);
	style.put(mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_CENTER);

	stylesheet.putCellStyle(shape, style);
    }

    // draw the places and the transitions
    public void drawNet() {
	init();
	int x = 50;
	int y = 50;

	int placeDistance = 100; // distance between places

	int transitionDistance = 50; // distance between transitions

	int transitionX = 100; // location of first transition
	int transitionY = 200;

	synchronized (places) {
	    int length = places.size();
	    System.out.println("this has .........." + length + " elements");
	    while (length > 0) {

		Place p = places.get(--length);
		insertPlace(p, x, y);

		x += placeDistance;
	    }
	    synchronized (transitions) {
		Place placeOut = null;
		Place placeIn = null;
		Arc edgeOut = null;
		Arc edgeIn = null;
		x = transitionX;
		y = transitionY;

		Iterator t = transitions.iterator();

		int lengthtrans = transitions.size();
		while (lengthtrans > 0) {

		    Transition tr = (Transition) transitions.get(--lengthtrans);

		    // draw the transition
		    insertTransition(tr, x, y);

		    synchronized (tr.getIn()) {
			Iterator tri = tr.getIn().iterator();
			while (tri.hasNext()) {
			    edgeIn = (Arc) tri.next();
			    placeIn = edgeIn.getPlace();
			    insertEdge(edgeIn, placeIn.getCell(), tr.getCell());
			}
		    }
		    synchronized (tr.getOut()) {
			Iterator tro = tr.getOut().iterator();
			while (tro.hasNext()) {
			    edgeOut = (Arc) tro.next();
			    placeOut = edgeOut.getPlace();
			    insertEdge(edgeOut, tr.getCell(), placeOut.getCell());
			}
		    }
		    x += transitionDistance;
		}
	    }

	}
    }

    private int[] getCurrentMarking(ArrayList<Place> places) {
	int[] currentMarking = new int[places.size()];
	for (Place p : places) {
	    currentMarking[p.getPosition()] = p.getTokens();
	}
	return currentMarking;
    }

    private boolean isShootable(int[][] matrixV, int transitionPosition) {
	return matrixV[transitionPosition][transitionPosition] > 0;
    }

    private int[][] createCalizMatrix(int[][] matrixV, int transitionPosition) {
	int[][] calizMatrix = new int[matrixV.length + 1][matrixV.length + 1];
	calizMatrix[matrixV.length][matrixV.length] = 1;

	for (int index = 0; index < matrixV.length; index++) {
	    calizMatrix[index][index] = matrixV[index][index];
	}
	calizMatrix[transitionPosition][transitionPosition] -= 1;

	return calizMatrix;
    }

    private int[] createCalizd(int[] d) {
	int[] calizd = new int[d.length + 1];
	System.arraycopy(d, 0, calizd, 0, d.length);
	calizd[calizd.length - 1] = 0;
	return calizd;
    }

    private int[][] createCalizPre(int[][] Pre, int[] M0) {
	int[][] calizPre = new int[Pre.length][Pre[0].length + 1];
	for (int index = 0; index < Pre.length; index++) {
	    System.arraycopy(Pre[index], 0, calizPre[index], 0, Pre[index].length);
	    calizPre[index][Pre[index].length] = M0[index];
	}
	return calizPre;
    }

    private int[][] createCalizC(int[][] C, int[] calizM, int[] M0) {
	int[][] calizC = new int[C.length][C[0].length + 1];
	for (int index = 0; index < C.length; index++) {
	    System.arraycopy(C[index], 0, calizC[index], 0, C[index].length);
	    calizC[index][C[index].length] = calizM[index] - M0[index];
	}
	return calizC;
    }

    // call repaint() after this is done to update the number of tokens in each
    // place
    public void run() {
	int[][] matrixV = new int[V.length][V.length];

	// insert V on matrix
	for (int index = 0; index < V.length; index++) {
	    matrixV[index][index] = V[index];
	}

	// clone initialMatrix
	int[][] matrixVInitial = matrixV.clone();

	while (running) {
	    try {
		petriThread.sleep(3000);
	    } catch (InterruptedException e) {
	    }
	    
	    StringBuilder matrixStringRepresentation = buildMatrixStringRepresentation(matrixV);
	    textAreaMatrix.setText(matrixStringRepresentation.toString());

	    synchronized (places) {
		synchronized (transitions) {
		    Iterator iterator = transitions.iterator();
		    while (iterator.hasNext()) {
			Transition transition = (Transition) iterator.next();
			synchronized (transition) {
			    if (transition != null && transition.isImmediate() && !transition.getIn().isEmpty()) {
				if (isEnabled(transition) && isShootable(matrixV, transition.getPosition())) {
				    enableTransition.add(transition);
				    System.out.println("Transition " + transition.getName() + " is enabled");
				}// if
			    }// if
			}// sync
		    }// while
		    if (!enableTransition.isEmpty()) {
			// Calcular heuristicas para todas las transiciones
			// habilitadas
			for (Transition transition : enableTransition) {
			    // agregar transicion nueva a C, Pre, d, calizMatrix
			    // en base a V
			    int[][] calizMatrix = createCalizMatrix(matrixV, transition.getPosition());
			    int[] calizd = createCalizd(d);
			    int[][] calizPre = createCalizPre(Pre, M0);
			    PetriNet calizPetri = PetriNet.createPetriNet(C, Pre, M0, Mf, d);
			    Transition calizTrans = calizPetri.getTransition(transition.getPosition());
			    calizPetri.fireMark(calizTrans, calizPetri.getPlaces(), true);
			    int[] calizM = calizPetri.getCurrentMarking(calizPetri.getPlaces());
			    int[][] calizC = createCalizC(C, calizM, M0);

			    // Obtener la heuristica del caliz
			    double heuristic = getHeuristic(calizC, calizPre, M0, calizMatrix, calizd);
			    // Guardar heuristica en la transicion;
			    transition.setHeuristic(heuristic);
			}
			Transition cheaperTransition = enableTransition.get(0);
			for (Transition transition : enableTransition) {
			    if (cheaperTransition.getProcessTime() + cheaperTransition.getHeuristic() > transition.getProcessTime() + transition.getHeuristic()) {
				cheaperTransition = transition;
			    }
			}

			fireMark(cheaperTransition, places, false);
			enableTransition.clear();

			// restar -1 a la diagonal
			matrixV[cheaperTransition.getPosition()][cheaperTransition.getPosition()] -= 1;
			// guarda secuencias de disparos
			transitionShooted.add(cheaperTransition);

		    }else{
			printFireSequence(transitionShooted);
			break;
		    }
		    
		}// sync
	    }// sync
	}
    }

    private StringBuilder buildMatrixStringRepresentation(int[][] matrixV) {
	StringBuilder matrixStringRepresentation = new StringBuilder();
	matrixStringRepresentation.append("Matríz de incidencia: ").append("\n");
	matrixStringRepresentation.append(pringMatrix(C));
	matrixStringRepresentation.append("\n\n");
	matrixStringRepresentation.append("Matríz Pre: ").append("\n");
	matrixStringRepresentation.append(pringMatrix(Pre));
	matrixStringRepresentation.append("\n\n");
	matrixStringRepresentation.append("Marcado inicial: ").append("\n");
	matrixStringRepresentation.append(pringMatrix(M0));
	matrixStringRepresentation.append("\n\n");
	matrixStringRepresentation.append("Vector de pesos de transiciones: ").append("\n");
	matrixStringRepresentation.append(pringMatrix(d));
	matrixStringRepresentation.append("\n\n");
	matrixStringRepresentation.append("Matriz de Parik: ").append("\n");
	matrixStringRepresentation.append(pringMatrix(matrixV));
	return matrixStringRepresentation;
    }

    private void printFireSequence(ArrayList<Transition> transitionShooted2) {
	StringBuilder builder = new StringBuilder();
	builder.append("Secuencia de disparos: \n\n");
	for(Transition t : transitionShooted){
	    builder.append(t.getName()).append("     ");
	}
	areaResult.setText(builder.toString());
    }

    private String pringMatrix(int[][] m) {
	StringBuilder builder = new StringBuilder();
	for (int i = 0; i < m.length; i++) {
	    builder.append("\n");
	    for (int j = 0; j < m[0].length; j++) {
		builder.append(m[i][j]).append(", ");
	    }
	}
	return builder.toString();
    }

    private String pringMatrix(int[] m) {
	StringBuilder builder = new StringBuilder();
	for (int i = 0; i < m.length; i++) {
	    builder.append(m[i]).append("\n");
	}
	return builder.toString();
    }

    /**
     * Returns herusitic calculation
     * 
     * @param incidenceMatrix
     * @param preMatrix
     * @param initialMarker
     * @param trigers
     * @param transitionsWeight
     * @return
     */
    private double getHeuristic(int[][] incidenceMatrix, int[][] preMatrix, int[] initialMarker, int[][] trigers, int[] transitionsWeight) {
	double heuristic = 0;
	try {
	    heuristic = simplex.resolveSimplex(incidenceMatrix, preMatrix, initialMarker, trigers, transitionsWeight);
	} catch (LpSolveException e) {
	    e.printStackTrace();
	}
	return heuristic;
    }
}
